#include <Scanner/scanner.h>
#include <cctype> // for isalpha(), isdigit()...

Scanner::Scanner() : source(std::string_view()), line(1), lineStart(0), offset(0), tokenOffset(0), tokenLine(1), tokenColumn(1)
{
}

void Scanner::initScanner(std::string_view source, int line)
{
    this->source = source;
    this->offset = 0;
    this->line = line;
    this->lineStart = 0;
}

Token Scanner::scanToken()
{
    // while(true)包裹起来是为了处理注释
    while (true)
    {
        advanceWhile(isspace);
        if (isAtEnd())
            return eofToken();

        tokenOffset = offset;
        tokenLine = line;
        tokenColumn = offset - lineStart + 1;

        char c = advance();
        switch (c)
        {
        case '#':
            // comment
            advanceTo('\n');
            continue;
        case '(':
            return makeToken(TokenType::LPAREN);
        case ')':
            return makeToken(TokenType::RPAREN);
        case '{':
            return makeToken(TokenType::LBRACE);
        case '}':
            return makeToken(TokenType::RBRACE);
        case ',':
            return makeToken(TokenType::COMMA);
        case '.':
            return makeToken(TokenType::DOT);
        case ';':
            return makeToken(TokenType::SEMICOLON);
        case ':':
            return makeToken(TokenType::COLON);
        case '-':
            return makeToken(match('-') ? TokenType::MINUS_MINUS : TokenType::MINUS);
        case '+':
            return makeToken(match('+') ? TokenType::PLUS_PLUS : TokenType::PLUS);
        case '*':
            return makeToken(TokenType::MUL);
        case '/':
            return makeToken(TokenType::DIV);
        case '?':
            return makeToken(TokenType::QUESTION_MARK);
        case '!':
            return makeToken(match('=') ? TokenType::BANGEQ : TokenType::BANG);
        case '=':
            return makeToken(match('=') ? TokenType::EQEQ : TokenType::EQ);
        case '<':
            return makeToken(match('=') ? TokenType::LTE : TokenType::LT);
        case '>':
            return makeToken(match('=') ? TokenType::GTE : TokenType::GT);
        case '"':
            return makeString();
        default:
            return isdigit(c) ? makeNumber() : isalpha(c) ? makeIdentifier()
                                                          : errorToken("Unexpected character");
        }
    }

    return Token();
}

char Scanner::advance()
{
    char next = source[offset++];

    if (next == '\n' && !isAtEnd())
    {
        line++;
        lineStart = offset;
    }

    return next;
}

char Scanner::peek()
{
    return offset >= source.size() ? '\0' : source[offset];
}

char Scanner::peekNext()
{
    return offset + 1 >= source.size() ? '\0' : source[offset + 1];
}

bool Scanner::isAtEnd() const
{
    return offset >= source.size();
}

bool Scanner::match(char expected)
{
    if (peek() != expected)
        return false;

    advance();
    return true;
}

void Scanner::advanceWhile(const std::function<bool(int)> &checker)
{
    while (checker(peek()))
        advance();
}

bool Scanner::advanceTo(char expected)
{
    while (!isAtEnd())
    {
        if (advance() == expected)
            return true;
    }

    return false;
}

std::string_view Scanner::lexeme()
{
    return source.substr(tokenOffset, offset - tokenOffset);
}

Token Scanner::makeToken(TokenType type)
{
    return Token{type, lexeme(), tokenLine, tokenColumn};
}

Token Scanner::makeString()
{
    bool escape = false;
    char c = advance();

    while (!isAtEnd() && (c != '\"' || escape))
    {
        if (escape)
        {
            // '\'后面的字符无条件加入，包括'"'
            escape = false;
        }
        else
        {
            if (c == '\\')
                escape = true;
        }
        c = advance();
    }

    return makeToken(TokenType::STRING);
}

Token Scanner::makeNumber()
{
    advanceWhile(isdigit);

    if (peek() == '.' && isdigit(peekNext()))
    {
        advance();
        advance();
        advanceWhile(isdigit);
    }
    else if (peek() == 'x' && isxdigit(peekNext()))
    {
        advance();
        advance();
        advanceWhile(isxdigit);
    }

    return makeToken(TokenType::NUMBER);
}

bool isIdentifier(int ch)
{
    return isalpha(ch) || isdigit(ch) || ch == '_';
}

Token Scanner::makeIdentifier()
{
    advanceWhile(isIdentifier);
    auto pair = reservedKeywords.find(lexeme());

    return makeToken(pair == reservedKeywords.end() ? TokenType::IDENTIFIER : pair->second);
}

Token Scanner::eofToken()
{
    return Token{TokenType::END_OF_FILE, "EOF", tokenLine, tokenColumn};
}

Token Scanner::errorToken(std::string_view message)
{
    return Token{TokenType::ERROR, message, line, offset - lineStart};
}

std::unordered_map<std::string_view, TokenType> Scanner::reservedKeywords = {
    {"nil", TokenType::NIL},
    {"true", TokenType::TRUE},
    {"false", TokenType::FALSE},
    {"var", TokenType::VAR},
    {"class", TokenType::CLASS},
    {"this", TokenType::THIS},
    {"super", TokenType::SUPER},
    {"if", TokenType::IF},
    {"else", TokenType::ELSE},
    {"for", TokenType::FOR},
    {"while", TokenType::WHILE},
    {"break", TokenType::BREAK},
    {"continue", TokenType::CONTINUE},
    {"func", TokenType::FUNC},
    {"return", TokenType::RETURN},
    {"and", TokenType::AND},
    {"or", TokenType::OR}};